
#include "Error.h"

#include "AbortException.h"

#include <QApplication>
#include <QDateTime>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include <QRegExp>
#include <QTemporaryFile>
#include <QTextStream>

#include <iostream>
#include <signal.h>
#include <time.h>

//! Diretório do usuário onde devem ser salvas as propriedades. 
const QString USER_PROPERTIES_DIR = ".rtscan/";

//! Nome do arquivo de log. 
const QString LOG_FILENAME = "Error.log";

//! Número máximo de entradas na stack trace
const int STACK_MAX_DEPTH = 25;

//! Número de entradas na stack trace para mostrar a causa do warning do qt.
const int QT_WARN_STACK_TRACE = 5;

const QString DATE_FORMAT = "yyyy/MM/dd hh:mm:ss";

//! Descritor do arquivo de log.
QFile Error::m_logFile;

//! Flag indicando se o log deve ser escrito no console. 
bool Error::m_logEchoEnabled = true;


//! Função que trata de operações ilegais no sistema
void faultHandler(int)
{
	// Previne que as operações seguintes entrem em loop
	signal(SIGSEGV, SIG_DFL);
	signal(SIGFPE, SIG_DFL);
	signal(SIGILL, SIG_DFL);

	// Aborta e escreve a stacktrace
	ERR_ABORT(
		"O programa executou uma operação ilegal. Informações \n"
		"desta falha foram salvas no log.");
}

void Error::initialize()
{
	// Define a função que irá capturar as falhas
	// SIGSERV - Segmentation violation
	// SIGSFPE - Floating point exception
	// SIGILL  - Illegal instruction
	signal(SIGSEGV, faultHandler);
	signal(SIGFPE, faultHandler);
	signal(SIGILL, faultHandler);

	// Instala o tratador de mensagens do Qt
	qInstallMsgHandler(ERR_qtMessageOutput);
	
	// Verifica se o diretório do usuário existe, caso contrário cria ele
	QString userDir = QString("%1/%2")
		.arg(QDir::homePath())
		.arg(USER_PROPERTIES_DIR);
	QDir directory;
	if(!directory.exists(userDir)) {
		directory.mkdir(userDir);
	}

	// Abre o arquivo de log
	QString logFileName = QString("%1%2")
		.arg(userDir)
		.arg(LOG_FILENAME);
	m_logFile.setFileName(logFileName);
	if(!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append)) {
		// Avisa que não foi possível abrir o arquivo de log
		std::cout << "Nao foi possível abrir o arquivo de log.";
	}
/*
	Error::log("============================================================");
	Error::log(Version::rtscanVersionData().toStdString().c_str());
	Error::log(QString("Revisão hg: %1").arg(Version::rtscanhgRevision()).toStdString().c_str());
	Error::log("============================================================\n");
*/
}


void Error::log(QString message)
{
	// Monta a mensagem final
	QString finalMessage = QString("%1 - %2\n")
		.arg(QDateTime::currentDateTime().toString(DATE_FORMAT))
		.arg(message);

	// Escreve no log
	if (m_logFile.isOpen()) {
		QDataStream stream(&m_logFile);
		stream << finalMessage;
	}

	// Escreve no console
	if (m_logEchoEnabled) {
		std::cout << finalMessage.toStdString();
	}
}


void Error::abortPrivate(
	const char * abortFilename,
	unsigned int abortFileLine,
	const QString & additionalInfo)
{
/*
	// Monta o header das informações adicionais
	QString message = QString(
		"Versao: %1\n"
		"Revisão do hg: %2\n"
		"Arquivo: %3\n"
		"Linha: %4\n"
		"%5")
		.arg(Version::rtscanVersionData())
		.arg(Version::rtscanhgRevision())
		.arg(abortFilename)
		.arg(abortFileLine)
		.arg(additionalInfo);
*/
	QString message( "" );

	QString stacktrace = getStackTrace();
	if(stacktrace != QString::null) {
		message.append(
			QString(
			"Stacktrace:\n"
			"\t%6")
			.arg(stacktrace.replace("\n", "\n\t")));
	}

	// Gera o log
	Error::log(QString(
		"ERRO FATAL - O programa será abortado.\n"
		"%1\n")
		.arg(message));

	throw AbortException(message);
}


void Error::abort(
	const char * abortFilename,
	unsigned int abortFileLine,
	QString message)
{
	QString additionalInfo = QString(
		"Mensagem: %1\n")
		.arg(message);
	abortPrivate(abortFilename, abortFileLine, additionalInfo);
}


void Error::info(QString message)
{
	QMessageBox::information(
		NULL,
		"INFORMAÇÃO",
		message,
		QMessageBox::Ok);
}


void Error::warn(QString message)
{
	Error::log(message);
	QMessageBox::warning(
		NULL,
		"AVISO",
		message,
		QMessageBox::Ok);
}


void Error::connect(
	const QObject * sender,
	const char * senderAsString,
	const char * signal,
	const QObject * receiver,
	const char * receiverAsString,
	const char * slot,
	const char * abortFileName,
	unsigned int abortFileline)
{
	if (!QObject::connect(sender, signal, receiver, slot)) {
		QString additionalInfo = QObject::tr(
			"Tipo: Falha em connect\n"
			"sender: %1\n"
			"signal: %2\n"
			"receiver: %3\n"
			"slot: %4\n")
			.arg(senderAsString)
			.arg(signal + 1)
			.arg(receiverAsString)
			.arg(slot + 1);
		abortPrivate(abortFileName, abortFileline, additionalInfo);
	}
}


void Error::disconnect(
	const QObject * sender,
	const char * senderAsString,
	const char * signal,
	const QObject * receiver,
	const char * receiverAsString,
	const char * slot,
	const char * abortFileName,
	unsigned int abortFileline)
{
	if (!QObject::disconnect(sender, signal, receiver, slot)) {
		QString additionalInfo = QObject::tr(
			"Tipo: Falha em disconnect\n"
			"sender: %1\n"
			"signal: %2\n"
			"receiver: %3\n"
			"slot: %4\n")
			.arg(senderAsString)
			.arg(signal + 1)
			.arg(receiverAsString)
			.arg(slot + 1);
		abortPrivate(abortFileName, abortFileline, additionalInfo);
	}
}


#ifdef DEBUG
void Error::assert(
	bool expression,
	const char * expressionAsString,
	const char * abortFileName,
	unsigned int abortFileline,
	QString message)
{
	if(!expression) {
		QString additionalInfo = QString(
			"Assertiva não satisfeita: %1\n")
			.arg(expressionAsString);

		if(message != QString::null) {
			additionalInfo.append(QString(
				"Mensagem: %1")
				.arg(message));
		}

		abortPrivate(
			abortFileName,
			abortFileline,
			additionalInfo);
	}
}
#endif


void ERR_qtMessageOutput(QtMsgType type, const char * message)
{
	switch (type) {
		case QtDebugMsg:
		case QtWarningMsg:
		case QtCriticalMsg:
		{
			Error::log(QString(
				"%1\n"
				"Stacktrace:\n"
				"%2\n")
				.arg(message)
				.arg(getStackTrace(QT_WARN_STACK_TRACE)));
			break;
		}
		case QtFatalMsg:
		{
			ERR_ABORT(message);
			break;
		}
		default:
		{	
			QString detailedMessage = QString(
				"Código de tipo de mensagem desconhecido (%1). Avise\n"
				"o responsável pelo software. Mensagem adicional:\n"
				"%2\n")
				.arg(type)
				.arg(message);
			ERR_ABORT(detailedMessage);
			break;
		}
	}
}

QString getStackTrace()
{
	return getStackTrace(STACK_MAX_DEPTH);
}


QString getStackTrace(int stackDepth)
{
/*
	ERR_ASSERT(stackDepth >= 1);
#if !(__4WIN__ || __APPLE_CC__)
	// Lemos a stacktrace
	void * functionAddresses[stackDepth];
	stackDepth = backtrace(functionAddresses, stackDepth);
	char ** functionCalls = backtrace_symbols(functionAddresses, stackDepth);
	
	// Identificamos a string de cada endereço e guardamos ela sequencialmente em uma string
	// A cara da expressão que será traduzida é:
	//      rtscan    (_ZNK1A12getStackTraceEv+0x12)      [0x804ad36]
	// Que podemos dividir em;
	//      (program) (simbolo)                (offset)   (endereço)
	QRegExp regexp("([^(]+)\\(([^)^+]+)(\\+[^)]+)\\)\\s\\[([^]]+)\\]");
	QString addrList;
	for(int i = 0; i < stackDepth; i++) {
		if(regexp.indexIn(functionCalls[i]) != -1) {
			// Lembrando, o endereço é o quarto elemento da expressão regular
			addrList.append(QString(" %1").arg(regexp.cap(4)));
		} else {
			Error::log(QString("Não foi possível identificar o edereço na expressão '%1'").arg(functionCalls[i]));
		}
	}

	// Foi alocado com malloc, deve desalocar com free
	free(functionCalls);

	// Construimos o comando que será executado
	QString programPath = qApp->arguments().at(0);
	QString cmd = QString("addr2line -f -i -C -e %1 %2")
		.arg(programPath)
		.arg(addrList);

	// Executamos o comando
	QProcess process;
	process.start(cmd);
	if(!process.waitForFinished()) {
		Error::log(QString("Falha ao executar o comando: %1").arg(cmd));
		return QString::null;
	}

	// O resultado para cada endereço é colocado em duas linhas, sendo a primeira o 
	// nome do simbolo e a segunda o seu arquivo e linha
	QStringList resultList(QString(process.readAllStandardOutput()).split("\n", QString::SkipEmptyParts));
	
	// Decobre o path com maior caminho
	int maxPathSize = 0;
	for(int i = 0; i < stackDepth; i++) {
		QString fileLine = resultList[(i * 2) + 1];
		maxPathSize = std::max(maxPathSize, fileLine.length());
	}

	// Monta a string com o resultado final
	QString stacktraceOutput;
	for(int i = 0; i < stackDepth; i++) {
		QString symbol = resultList[i * 2];
		QString fileLine = resultList[(i * 2) + 1];

		// Criamos espaços para gerar uma formatação mais clara
		int numSpaces = maxPathSize - fileLine.length() + 3;
		QString spaces(numSpaces, ' ');
		
		stacktraceOutput.append(QString("%1%2%3\n")
			.arg(fileLine)
			.arg(spaces)
			.arg(symbol));
	}

	return stacktraceOutput;
#else
	return QString::null;
#endif
*/
	return QString::null;
}
